// Package cast2 实现将U语言转换为C语言的转换器
package cast2

import (
	"fmt"
	"sort"
	"strings"

	"gitee.com/u-language/u-language/ucom/ast2"
	"gitee.com/u-language/u-language/ucom/internal/utils"
)

type UtoC struct {
	headerFile   []ast2.CDecl
	PackageName  string
	FileName     string
	varInit      []*ast2.VarNode
	Nodes        []ast2.Node
	importAll    []*Package
	havaInitFunc bool
	Thread       bool
}

func newUtoC(tree *ast2.Tree) UtoC {
	c := UtoC{}
	c.PackageName = tree.PackageName
	c.Nodes = tree.Nodes
	return c
}

func (c *UtoC) Parser(tree *ast2.Tree) {
	c.Nodes = tree.Nodes
	c.PackageName = tree.PackageName
	c.havaInitFunc = *tree.HaveInitFunc
	c.headerFile = tree.CHeaderFile.Data
	c.varInit = tree.VarInitTable.InitOrder()
	for i := range *tree.ImportPackage {
		c.importAll = append(c.importAll, NewPackage(false, (*tree.ImportPackage)[i], false))
	}
}

func (c *UtoC) C(firstcall bool) string {
	var buf strings.Builder
	buf.WriteString(includeStr)
	if firstcall {
		for i := range c.importAll {
			c.importAll[i].Oupput(&buf, c.importAll[i].GenerateheaderFile())
		}
	}
	generateHeaderFile(c.headerFile, &buf)
	Init := generateInit(c.PackageName, c.havaInitFunc, c.varInit, c.importAll, &buf)
	generateCFile(c.Nodes, &buf)
	buf.WriteString(Init)
	return buf.String()
}

// generateInit 生成隐式调用init函数 返回init函数定义
//   - PackageName 是包名
//   - haveInitFunc是否有自定义init函数
//   - node 是变量初始化节点
//   - fbuf 写入声明
func generateInit(PackageName string, haveInitFunc bool, node []*ast2.VarNode, ImportAll []*Package, fbuf *strings.Builder) string {
	var buf strings.Builder
	vil := len(node)
	var decl string = utils.GeneratePackageSymbol(PackageName, "init")
	//生成隐式init函数声明
	fbuf.WriteString("// --- 隐式init函数声明 ---\n")
	fbuf.WriteString("void ")
	fbuf.WriteString(decl)
	fbuf.WriteString("();\n")
	//生成隐式init函数定义
	f := ast2.FuncNode{}
	f.FuncInfo = &ast2.FuncInfo{}
	f.Name = decl
	f.C(&buf)
	fbuf.WriteString("\n")

	//生成隐式init函数函数体
	buf.WriteString("\n")
	for i := 0; i < vil; i++ {
		buf.WriteString(node[i].Name)
		buf.WriteString("=")
		node[i].Value.C(&buf)
		buf.WriteString(";")
	}
	if haveInitFunc { //如果有自定义的init函数
		buf.WriteString("// --- 调用自定义init函数 ---\n")
		//调用自定义init函数
		buf.WriteString(utils.GeneratePackageSymbol(PackageName, "init__user"))
		buf.WriteString("();\n")
	}
	for i := range ImportAll {
		//TODO:按语言规范实现init
		buf.WriteString(ImportAll[i].packageName + "__init();")
	}
	buf.WriteString("\n}\n")
	return buf.String()
}

// generateCFile 将C代码转换为字符串写入buf
func generateCFile(nodes []ast2.Node, buf *strings.Builder) {
	for i := 0; i < len(nodes); i++ {
		nodes[i].C(buf)
		buf.WriteString("\n")
	}
}

// generateHeaderFile 生成头文件的内容
// 假设inAutoFreeFuncInfo已经排序了
func generateHeaderFile(slice []ast2.CDecl, buf *strings.Builder) {
	sort.Slice(slice, func(i int, j int) bool { //排序是为了保证可复现的构建，和类型声明在最前面
		return nodeCmp(i, j, slice)
	})
	for _, v := range slice {
		v.CDecl(buf)
		buf.WriteString("\n")
	}

}

// nodeCmp i小于j返回true
//
// 比较规则
//   - 枚举最小
//   - 结构体第二小
//   - 其他依据行号大小，行号小的更小，如果行号相同，比较名称
func nodeCmp(i, j int, slice []ast2.CDecl) bool {
	switch in := slice[i].(type) {
	case *ast2.EnumDecl:
		switch jn := slice[j].(type) {
		case *ast2.EnumDecl:
			return in.Name < jn.Name
		default:
			return true
		}
	case *ast2.StructDecl:
		switch jn := slice[j].(type) {
		case *ast2.EnumDecl:
			return false
		case *ast2.StructDecl:
			return in.Name < jn.Name
		default:
			return true
		}
	}
	switch slice[j].(type) {
	case *ast2.EnumDecl:
		return false
	case *ast2.StructDecl:
		return false
	}
	in, il := getNameAndLine(slice[i])
	jn, jl := getNameAndLine(slice[j])
	switch {
	case il < jl:
		return true
	case il > jl:
		return false
	}
	// 如果i和j的行数相等，则按节点名称进行比较
	return in < jn
}

func getNameAndLine(n ast2.CDecl) (string, int) {
	switch n := n.(type) {
	case *ast2.VarNode:
		return n.Name, n.LineNum
	case *ast2.ConstNode:
		return n.Name, n.LineNum
	case *ast2.FuncNode:
		return n.Name, n.LineNum
	case *ast2.MethodNode:
		return n.Name, n.LineNum
	case *ast2.StructDecl:
		return n.Name, n.LineNum
	case *ast2.EnumDecl:
		return n.Name, n.LineNum
	}
	panic(fmt.Sprintf("%T", n))
}

const includeStr = `#include <stdbool.h> 
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

`
